namespace SimpleWebServices.Web.Mvc
{
    using System;
    using System.Globalization;
    using System.Net;
    using System.Security.Principal;
    using System.Threading;
    using System.Web;
    using System.Web.Mvc;
    using Moq;
    using SimpleWebServices;
    using SimpleWebServices.Configuration;
    using SimpleWebServices.Security.Principal;
    using Xunit;

    public sealed class IsInRoleAttributeFacts
    {
        [Fact]
        public void typedef()
        {
            Assert.True(new ObjectExpectations<IsInRoleAttribute>()
                .DerivesFrom<ActionFilterAttribute>()
                .IsConcreteClass()
                .IsSealed()
                .NoDefaultConstructor()
                .IsDecoratedWith<AttributeUsageAttribute>()
                .Result);
        }

        [Fact]
        public void ctor_string_string()
        {
            Assert.NotNull(new IsInRoleAttribute("Guest,User", "Cookie,Digest"));
        }

        [Fact]
        public void prop_Roles_get()
        {
            string expected = "Guest,User";
            string actual = new IsInRoleAttribute(expected, "Cookie,Digest").Roles;

            Assert.Equal<string>(expected, actual);
        }

        [Fact]
        public void prop_Schemes_get()
        {
            string expected = "Cookie,Digest";
            string actual = new IsInRoleAttribute("Guest,User", expected).Schemes;

            Assert.Equal<string>(expected, actual);
        }

        [Fact]
        public void ctor_stringEmpty_string()
        {
            Assert.Throws<ArgumentOutOfRangeException>(() => new IsInRoleAttribute(string.Empty, "Cookie"));
        }

        [Fact]
        public void ctor_stringNull_string()
        {
            Assert.Throws<ArgumentNullException>(() => new IsInRoleAttribute(null as string, "Cookie"));
        }

        [Fact]
        public void ctor_string_stringEmpty()
        {
            Assert.Throws<ArgumentOutOfRangeException>(() => new IsInRoleAttribute("User", string.Empty));
        }

        [Fact]
        public void ctor_string_stringNull()
        {
            Assert.Throws<ArgumentNullException>(() => new IsInRoleAttribute("User", null as string));
        }

        [Fact]
        public void op_OnActionExecuting_ActionExecutingContextNull()
        {
            Assert.Throws<ArgumentNullException>(() => new IsInRoleAttribute("User", "Cookie").OnActionExecuting(null));
        }

        [Fact]
        public void op_OnActionExecuting_ActionExecutingContext_whenAllowed()
        {
            User user = new TestUser("Cookie", true)
            {
                Roles =
                {
                    "User"
                }
            };

            var mock = new Mock<HttpContextBase>();
            mock.SetupGet<IPrincipal>(x => x.User).Returns(user).Verifiable();

            new IsInRoleAttribute("Guest,User", "Cookie").OnActionExecuting(new ActionExecutingContext { HttpContext = mock.Object });

            mock.VerifyAll();
        }

        [Fact]
        public void op_OnActionExecuting_ActionExecutingContext_whenUserNotAuthenticated()
        {
            CultureInfo culture = Thread.CurrentThread.CurrentUICulture;

            Default.Clear();
            ServiceLocator.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;
                
                Default.Register<Unauthorized>("<resource />");

                ServiceLocator.Register<IViewResult, UnauthorizedViewResult>("Unauthorized");
                ServiceLocator.Register<IWwwAuthenticate, WwwAuthenticateCookie>("Cookie");

                User user = new TestUser("Cookie", false)
                {
                    Roles =
                    {
                        "User"
                    }
                };

                var mock = new Mock<HttpContextBase>();
                mock.Setup(x => x.Response.Cache.SetCacheability(HttpCacheability.NoCache)).Verifiable();
                mock.Setup(x => x.Response.Clear()).Verifiable();
                mock.Setup(x => x.Response.Headers.Add("WWW-Authenticate", It.Is<string>(value => value == "Cookie"))).Verifiable();
                mock.SetupSet(x => x.Response.StatusCode).Callback(actual => Assert.Equal<int>(actual, (int)HttpStatusCode.Unauthorized)).Verifiable();
                mock.SetupGet<IPrincipal>(x => x.User).Returns(user).Verifiable();
                HttpContextFactory.Mock = mock.Object;

                ActionExecutingContext filterContext = new ActionExecutingContext
                {
                    HttpContext = mock.Object
                };

                new IsInRoleAttribute("Guest,User", "Cookie").OnActionExecuting(filterContext);

                Assert.IsAssignableFrom<UnauthorizedViewResult>(filterContext.Result);

                mock.VerifyAll();
            }
            finally
            {
                Thread.CurrentThread.CurrentUICulture = culture;

                DateTimeFactory.Reset();
                Default.Reset();
                ServiceLocator.Reset();
                HttpContextFactory.Reset();
            }
        }

        [Fact]
        public void op_OnActionExecuting_ActionExecutingContext_whenUserNotInRole()
        {
            CultureInfo culture = Thread.CurrentThread.CurrentUICulture;

            Default.Clear();
            ServiceLocator.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;
                
                Default.Register<Unauthorized>("<resource />");

                ServiceLocator.Register<IViewResult, UnauthorizedViewResult>("Unauthorized");
                ServiceLocator.Register<IWwwAuthenticate, WwwAuthenticateCookie>("Cookie");

                User user = new TestUser("Cookie", true)
                {
                    Roles =
                    {
                        "Guest"
                    }
                };

                var mock = new Mock<HttpContextBase>();
                mock.Setup(x => x.Response.Cache.SetCacheability(HttpCacheability.NoCache)).Verifiable();
                mock.Setup(x => x.Response.Clear()).Verifiable();
                mock.Setup(x => x.Response.Headers.Add("WWW-Authenticate", It.Is<string>(value => value == "Cookie"))).Verifiable();
                mock.SetupSet(x => x.Response.StatusCode).Callback(actual => Assert.Equal<int>(actual, (int)HttpStatusCode.Unauthorized)).Verifiable();
                mock.SetupGet<IPrincipal>(x => x.User).Returns(user).Verifiable();
                HttpContextFactory.Mock = mock.Object;

                ActionExecutingContext filterContext = new ActionExecutingContext
                {
                    HttpContext = mock.Object
                };

                new IsInRoleAttribute("User", "Cookie").OnActionExecuting(filterContext);

                Assert.IsAssignableFrom<UnauthorizedViewResult>(filterContext.Result);

                mock.VerifyAll();
            }
            finally
            {
                Thread.CurrentThread.CurrentUICulture = culture;

                DateTimeFactory.Reset();
                Default.Reset();
                ServiceLocator.Reset();
                HttpContextFactory.Reset();
            }
        }
    }
}